package com.hzit.search.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.MultiMatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.util.ObjectBuilder;
import com.alibaba.fastjson.JSON;
import com.hzit.search.service.ISearchService;
import com.hzit.search.vo.ItemVo;
import com.ruoyi.common.core.constant.HzitConstants;
import com.ruoyi.system.api.domain.Item;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.function.Function;

/**
 * 类名：
 * 作者：WF
 * 功能：
 */
@Service
public class SearchServiceImpl implements ISearchService {
	@Autowired
	private ElasticsearchClient elasticsearchClient;
	@Autowired
	private StringRedisTemplate redisTemplate;

	//1. 开始查询
	@Override
	public Map<String, Object> search(ItemVo vo) throws IOException {
		Map<String, Object> resultMap = new HashMap<>();
		//1.1 定义存放查询到的商品列表
		List<Item> itemList = new ArrayList<>();
		//1.2 定义存放分类列表的集合
		List<String> categoryList = new ArrayList<>();
		//1.3 定义SearchRequest对象
		SearchRequest request = null;
		//1.4 定义过滤查询的集合对象
		List<Query> filterQuery = new ArrayList<>();
		//1.5 定义排序集合
		List<SortOptions> sortOptions = new ArrayList<>();
		//1.6 得到当前页及每页大小
		int page = vo.getPage();                // 当前页
		int pageSize = vo.getPageSize();        // 每页大小
		//1.7 定义分类查询对象
		if(StrUtil.isNotBlank(vo.getCategory())){
			//1.7.1 构造分类查询对象
			Query categoryQuery = TermQuery.of(tq -> tq.field(HzitConstants.CATEGORY_FIELDS).value(vo.getCategory()))._toQuery();
			//相当于：
			// Query categoryQuery = TermQuery.of(new Function<TermQuery.Builder, ObjectBuilder<TermQuery>>() {
			// 	@Override
			// 	public ObjectBuilder<TermQuery> apply(TermQuery.Builder builder) {
			// 		return builder.field(HzitConstants.CATEGORY_FIELDS).value(vo.getCategory());
			// 	}
			// })._toQuery();
			//1.7.2 将分类查询放到过滤查询集合中
			filterQuery.add(categoryQuery);
		}
		//1.8 根据品牌查询
		if(StrUtil.isNotBlank(vo.getBrand())){
			//1.8.1 构造品牌查询对象
			Query brandQuery = TermQuery.of(tq -> tq.field(HzitConstants.BRAND_FIELDS).value(vo.getBrand()))._toQuery();
			//1.8.2 将品牌查询对象放到过滤查询集合中
			filterQuery.add(brandQuery);
		}
		//1.9 根据规格进行查询
		if(CollectionUtil.isNotEmpty(vo.getSpec())){
			//1.9.1 遍历集合，得到每个规格
			List<String> spec = vo.getSpec();
			for (String s : spec) {
				String[] split = s.split(":");      // split[0]: 规格名字 split[1]: 规格选项的值
				// 1.9.2 构造一个规格查询的对象
				Query specQuery = TermQuery.of(tq -> tq.field(HzitConstants.SPEC_QUERY_PREFIX + split[0] + HzitConstants.SPEC_QUERY_SUFFIX).value(split[1]))._toQuery();
				//1.9.3 放到过滤查询的集合中
				filterQuery.add(specQuery);
			}
		}
		//1.10 价格区间查询
		if(StrUtil.isNotBlank(vo.getPrice())){
			//1.10.1 得到价格的值
			String price = vo.getPrice();
			//1.10.2 对价格进行拆分
			String[] split = price.split("-");
			//1.10.3 构造价格区间查询的Query对象
			Query priceQuery = null;
			if(split[1].equals("*")){
				//1.10.4 价格只在指定范围以上（大于等于指定值）
				priceQuery = RangeQuery.of(rq -> rq.field(HzitConstants.PRICE_FIELDS).gte(JsonData.of(split[0])))._toQuery();
			}else{
				priceQuery = RangeQuery.of(rq -> rq.field(HzitConstants.PRICE_FIELDS).gte(JsonData.of(split[0])).lte(JsonData.of(split[1])))._toQuery();
			}
			//1.10.4 将价格查询对象放到过滤查询列表中
			filterQuery.add(priceQuery);
		}
		//1.11 定义价格排序
		if(StrUtil.isNotBlank(vo.getSort())){
			String sort = vo.getSort();
			String[] split = sort.split(":");      // split[0]: 代莆排序规则 split[1]: 代表排序字段
			SortOptions options = null;
			//1.11.1 构造SortOption对象，代表一次排序
			if(split[0].equals("asc")){
				options = SortOptions.of(so -> so.field(sf -> sf.field(split[1]).order(SortOrder.Asc)));
			}else if(split[0].equals("desc")){
				options = SortOptions.of(so -> so.field(sf -> sf.field(split[1]).order(SortOrder.Desc)));
			}
			//1.11.2 放到sortOption集合中
			sortOptions.add(options);
		}
		//1.12 输入关键字查询
		if(StrUtil.isNotBlank(vo.getKeywords())){       // 输入关键字时的查询
			//1.12.1 定义多字段匹配查询
			Query multiMatchQuery = MultiMatchQuery.of(mq -> mq
					.fields(HzitConstants.MULTI_MATCH_QUERY_FIELDS)       // 字段名称
					.query(vo.getKeywords())                                 // 查询的值
			)._toQuery();
			//1.12.2 将多字段匹配查询的对象放到filterQuery集合中
			filterQuery.add(multiMatchQuery);
			//1.13 构造查询请求对象
			request = SearchRequest.of(sr -> sr
										//1.13.1 组合查询--》过滤查询
										.query(q -> q.bool(qb -> qb
												.filter(filterQuery)))
										//1.13.2 高亮查询
										.highlight(hl -> hl
												.fields(HzitConstants.HIGHLIGNT_FIELDS, hlf -> hlf           // title: 设置高亮查询的字段
												.preTags(HzitConstants.HIGHLIGHT_PRETAGS)
														.postTags(HzitConstants.HIGHT_POSTTAGS)))  // 设置高亮查询的前缀和后缀
										//1.13.3 聚合查询
										.aggregations(HzitConstants.AGGREGATIONS_KEYS,agg -> agg
												.terms(at -> at
														.field(HzitConstants.AGGREGATIONS_FIELDS)))
										//1.13.4 排序查询
										.sort(sortOptions)
										//1.13.5 分页查询
										.from((page - 1) * pageSize)             // 从哪条记录开始查询
										.size(pageSize)                          // 每页显示条数
			);
		}else{                                                                  // 没有输入关键字时的查询
			request = SearchRequest.of(sr -> sr
					.index("item2")
					.query(q -> q.matchAll(ma -> ma))
					// 聚合查询
					.aggregations(HzitConstants.AGGREGATIONS_KEYS,
							agg -> agg.terms(at -> at
									.field(HzitConstants.AGGREGATIONS_FIELDS)))
			);
		}

		//1.14 开始查询
		SearchResponse<Item> searchResponse = elasticsearchClient.search(request, Item.class);
		//1.15 从查询结果中获取到查询的数据
		HitsMetadata<Item> hits = searchResponse.hits();
		//1.16 得到总记录数
		assert hits.total() != null;
		long total = hits.total().value();
		//1.17 计算总页数,注意使用（）进行类型转换为再计算
		int totalPage = (int) Math.ceil(total / (pageSize*1.0));
		List<Hit<Item>> hitList = hits.hits();
		//1.18 处理查询结果
		for (Hit<Item> itemHit : hitList) {
			//1.18.1 得到高亮数据
			Map<String, List<String>> highlight = itemHit.highlight();          // key：高亮查询的字段名,高亮查询后的字段值（包含前缀和后缀名）
			//1.18.2 根据高亮字段得到值
			List<String> list = highlight.get(HzitConstants.HIGHLIGNT_FIELDS);
			Item item = itemHit.source();     // 得到未设置高亮之前的哪条数据
			if(CollectionUtil.isNotEmpty(list)){  // 相当于：list != null && list.size() != 0
				String title = list.get(0);       // 得到高亮数据，包含前缀和后缀字段
				item.setTitle(title);
			}
			itemList.add(item);               // 放到商品集合中
		}
		//1.19 得到总的聚合
		Map<String, Aggregate> aggregations = searchResponse.aggregations();        // key：代表聚合的名字，值，代表聚合出来的结果
		//1.19.1 得到分组后的数据
		List<StringTermsBucket> termsBuckets = aggregations.get(HzitConstants.AGGREGATIONS_KEYS).sterms().buckets().array();
		if(CollectionUtil.isNotEmpty(termsBuckets)){
			for (StringTermsBucket termsBucket : termsBuckets) {
				//1.19.2 得到分类的值
				String key = termsBucket.key();
				//1.19.3 将分类的值放到分类集合中
				categoryList.add(key);
			}
		}
		//1.20 根据商品分类，从redis中得到品牌及规格列表
		String category = null;
		if(StrUtil.isNotBlank(vo.getCategory())){       // 代表用户点击了分类
			category = vo.getCategory();
		}else if(CollectionUtil.isNotEmpty(categoryList)){
			category = categoryList.get(0);             // 用户没有点击分类
		}
		Map<String, List<Map>> brandAndSpecList = findBrandAndSpecList(category);
		//1.21 将itemList放到resultMap中
		resultMap.put(HzitConstants.ITEM_LIST,itemList);            // 商品列表
		resultMap.put(HzitConstants.CATEGORY_LIST,categoryList);    // 分类列表
		resultMap.putAll(brandAndSpecList);                         // 存放品牌及规格列表
		resultMap.put(HzitConstants.TOTAL_PAGE,totalPage);          // 总页数
		resultMap.put(HzitConstants.TOTAL,total);                   // 总记录数
		return resultMap;
	}

	/**
	 * 根据分类名字从redis中查询出品牌及规格列表
	 * @param category
	 * @return
	 */
	private Map<String, List<Map>> findBrandAndSpecList(String category) {
		Map<String, List<Map>> map = new HashMap<>();
		List<Map> brandList = new ArrayList<>();
		List<Map> specList = new ArrayList<>();
		if(StrUtil.isNotBlank(category)){
			//1. 根据分类找到模板id
			String id = (String) redisTemplate.opsForHash().get(HzitConstants.ITEMCAT_LIST, category);
			//2. 根据模板id，查询出品牌及规格列表
			String brandStr = (String) redisTemplate.opsForHash().get(HzitConstants.BRAND_LIST, id);
			brandList = JSON.parseArray(brandStr,Map.class);
			//3. 根据模板id，查询出规格及规格选项列表
			String specStr = (String) redisTemplate.opsForHash().get(HzitConstants.SPEC_LIST, id);
			specList = JSON.parseArray(specStr, Map.class);
		}
		//4. 将规格及品牌列表放到map中
		map.put(HzitConstants.SPEC_LIST,specList);
		map.put(HzitConstants.BRAND_LIST,brandList);
		return map;
	}
}
